<?php

class P2X2P_Server extends P2X2P{
	private $users = array();
	public 	$requireAuthentication = false;
	public $classes = array();
	public static $__P2X2P_Server = false;
	
	const GLOBAL_CLASS = -100;
	
	public function __construct($useSystem=true, $requireAuthentication=false) {
		parent::__construct();
		
		if(self::$__P2X2P_Server) {
			Error::handler(null, 'P2X2P Error - Only one RPC Server Can Exist', __FILE__, __LINE__);
		} else {
			self::$__P2X2P_Server = $this;
			
			$this->requireAuthentication = $requireAuthentication;
			
			if($useSystem) {
				$this->addClass('System');
			}
		}
	}
	
	public function addUser() {
		// ====================
		// = !! Deprecated !! =
		// ====================
		Log::append("Deprecated", "P2X2P_Server:addUser()");
		$args=func_get_args();
		call_user_func_array(array($this, 'add_user'), $args);
	}
	
	public function addClass() {
		// ====================
		// = !! Deprecated !! =
		// ====================
		Log::append("Deprecated", "P2X2P_Server:addClass()");
		$args=func_get_args();
		call_user_func_array(array($this, 'add_class'), $args);
	}
	
	public function addMethod() {
		// ====================
		// = !! Deprecated !! =
		// ====================
		Log::append("Deprecated", "P2X2P_Server:addMethod()");
		$args=func_get_args();
		call_user_func_array(array($this, 'add_method'), $args);
	}
	
	public function methodFault() {
		// ====================
		// = !! Deprecated !! =
		// ====================
		Log::append("Deprecated", "P2X2P_Server:methodFault()");
		$args=func_get_args();
		call_user_func_array(array($this, 'method_fault'), $args);
	}
		
	public function Start_Server() {
		// ====================
		// = !! Deprecated !! =
		// ====================
		Log::append("Deprecated", "P2X2P_Server:Start_Server()");
		$args=func_get_args();
		call_user_func_array(array($this, 'start'), $args);
	}
	
	public function Reply() {
		// ====================
		// = !! Deprecated !! =
		// ====================
		Log::append("Deprecated", "P2X2P_Server:Reply()");
		$args=func_get_args();
		call_user_func_array(array($this, 'sendResponse'), $args);
	}
	
	public function add_user($username, $password, $allowHosts) {
		$allowHosts = str_replace('%','([0-9]{1})',$allowHosts);
		$this->users[$username]=array('P'=>$password,'H'=>$allowHosts);
	}
	
	private function authenticate() {
		
		if(isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['PHP_AUTH_PW'])) {
			$U = $_SERVER['PHP_AUTH_USER'];
			$P = $_SERVER['PHP_AUTH_PW'];
			$H = $_SERVER['REMOTE_ADDR'];
			
			if(isset($this->users[$U])) {
				if($this->users[$U]['P']==$P) {
					if(ereg($this->users[$U]['H'],$H))
					return true;
					#else
					#$fault = $this->methodFault(152,"Permission Denined For User ($U) - Invalid Host ($H)");
				}
				#else
				#$fault = $this->methodFault(151,"Permission Denined For User ($U) - Incorrect Password");
			}
			#else
			#$fault = $this->methodFault(150,"Permission Denined For User ($U) - No Such User");
		}
		
		header('WWW-Authenticate: Basic realm="*******"');
	    header('HTTP/1.0 401 Unauthorized');
	    header('content-type: text/html');
	    echo '<h3>401 - Unauthorized</h3>';
	    exit;
	}
	
	public function add_class($className)
	{
		if($className=='System') {
			$className='P2X2P_System';
		}
		
		if(!class_exists($className)) {
			Error::handler(null, "P2X2P Error - Class ($className) cannot be added, it doesn't exist", __FILE__, __LINE__);
		}
		
		$class = new ReflectionClass($className);
		
		foreach($class->getMethods() as $method) {
			$this->addMethod($method->name,$method->class);
		}
	}
	
	public function add_method($methodName,$className=null)
	{
		if($className) {
			if(!class_exists($className)) {
				Error::handler(null, "P2X2P Error - Class ($className) cannot be added, it doesn't exist", __FILE__, __LINE__);
			}
			
			if(!method_exists($className, $methodName)) {
				Error::handler(null, "P2X2P Error - Method ($methodName) cannot be added, it doesn't exist", __FILE__, __LINE__);
			}
			
			$methodReflection = new ReflectionMethod($className,$methodName);
			
			if(!$methodReflection->isStatic() || !$methodReflection->isPublic())
			return false;
		} else {
			if(!function_exists($method_name)) {
				return(false);
			}
			
			$methodReflection = new ReflectionFunction($methodName);
			$className=GLOBAL_CLASS;
		}
		
		$methodInfo['required']=$methodReflection->getNumberOfRequiredParameters();
		$methodInfo['total']=$methodReflection->getNumberOfParameters();
		$this->classes[$className][$methodName]=$methodInfo;
		
		return true;
	}
	
	public function method_fault($code, $string=null) {
		
		if($string == null) {
			$string = $this->error($code);
		}
		
		
		$fault = array('faultCode'=>$code,'faultString'=>$string);
		$methodResponseNode = $this->DOM->createElement('methodResponse');
		$faultNode = $this->DOM->createElement('fault');
		$faultNode->appendChild($this->encode_struct($fault));
		$methodResponseNode->appendChild($faultNode);
		
		$this->sendResponse($methodResponseNode);
		exit;
	}
	
	public function parse_request() {
		
		// ============
		// = Validate =
		// ============
		if(!isset($GLOBALS["HTTP_RAW_POST_DATA"]))
		$this->method_fault(100);
		
		if(!@$this->DOM->loadXML($GLOBALS["HTTP_RAW_POST_DATA"]))
		$this->method_fault(102);
		
		if(!@$this->DOM->schemaValidate(Plex::$BASE_DIR . 'driver/P2X2P/xmlrpc.xsd'))
		$this->method_fault(101);
		
		// =========
		// = Parse =
		// =========
		
		$methodCallNode = $this->DOM->getElementsByTagName('methodCall')->item(0);
		$methodNameNode = $methodCallNode->getElementsByTagName('methodName')->item(0);
		$parametersNode = $methodCallNode->getElementsByTagName('params')->item(0);
		$parameterNodes = $parametersNode->getElementsByTagName('param');
		
		$methodName = $methodNameNode->nodeValue;
		$methodParameters = array();
		
		for($i=0; $i<$parameterNodes->length; $i++) {
			$methodParameters[] = $this->decode($parameterNodes->item($i)->firstChild);
		}
		
		$methodInfo = explode('.',$methodName);
		
		if(count($methodInfo)==1) {
			$className = self::GLOBAL_CLASS;
			$methodName = $methodInfo[0];
		} else {
			$className = $methodInfo[0];
			$methodName = $methodInfo[1];
		}
		
		if($className=='System') {
			$className='P2X2P_System';
		}
		
		if(!isset($this->classes[$className][$methodName])) {
			$this->method_fault(200);
		}
		
		if(count($methodParameters)<$this->classes[$className][$methodName]['required']) {
			$this->method_fault(201);
		}
		
		if(count($methodParameters)>$this->classes[$className][$methodName]['total']) {
			$this->method_fault(201);
		}
		
		if($className==self::GLOBAL_CLASS)
		$value=call_user_func_array($methodName,$methodParameters);
		else 
		$value=call_user_func_array(array($className,$methodName),$methodParameters);
		
		
		$this->method_response($value);
	}
	
	private function method_response($return)
	{
		$methodResponseNode = $this->DOM->createElement('methodResponse');
		
		$parametersNode = $this->DOM->createElement('params');
		$parameterNode = $this->DOM->createElement('param');
		
		$methodResponseNode->appendChild($parametersNode);
		$parametersNode->appendChild($parameterNode);
		$parameterNode->appendChild($this->encode($return));
		
		$this->sendResponse($methodResponseNode);
	}
	
	private function sendResponse($responseNode) {
		$this->resetDOM();		
		header('content-type: text/xml');
		$this->DOM->appendChild($responseNode);
		echo $this->DOM->saveXML();
	}
	
	public function start()
	{
		if($this->requireAuthentication)
		$this->authenticate();

		$this->parse_request();
	}
}